package com.me.controllers;

import com.me.Utils.ServUtil;
import com.me.annotation.Admin;
import com.me.annotation.Authorize;
import com.me.model.User;
import org.apache.commons.fileupload.FileItem;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author 吕旺平
 * @date 2019/10/20 15:58:01
 * @description
 */
public abstract class Controller {
//	private static Map<String, Object> params;
	protected HttpServletRequest req;
	protected HttpServletResponse resp;
	protected HttpSession session;
	protected User u;
	protected Object Model;
	protected String controller;
	protected String method;

	public Controller(HttpServletRequest req, HttpServletResponse resp) {
		this.req = req;
		this.resp = resp;
		session = req.getSession();
		try {
			req.setCharacterEncoding("utf8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		u = (User) session.getAttribute("user");
		controller = this.getClass().getSimpleName();
		controller = controller.substring(0, controller.indexOf("Controller"));
	}

	protected void setRedirectParam(String key, Object value) {
		setRedirectParam(key, value, false);
	}

	protected void setRedirectParam(String key, Object value, boolean reset) {
		Map<String, Object> params = (Map<String, Object>)getSessionAttr("params");
		if (params == null) {
			params = new HashMap<String, Object>();
			setSessionAttr("params", params);
		}
		if (reset) {
			params.clear();
		}
		params.put(key, value);
	}

	public boolean isParamEnabled() {
		return getSessionAttr("params") != null &&  ((Map<String, Object>)getSessionAttr("params")).size() != 0;
	}

	public void resetParam() {
		((Map<String, Object>)getSessionAttr("params")).clear();
	}

	public abstract String index(String arg);

	public String excute(String arg, String param) {
		this.method = arg;
		String type = req.getMethod();
		User u = (User) getSessionAttr("user");
		Method method = null;
		try {
			method = type.toLowerCase().equals("post") ?
					this.getClass().getMethod(arg) :
					this.getClass().getMethod(arg, String.class);

			if (
					method.getAnnotation(Authorize.class) != null ||
							method.getAnnotation(Admin.class) != null ||
							Controller.class.getAnnotation(Authorize.class) != null
			) {
				if (u == null) {
					if (req.getHeader("X-Requested-With") != null && req.getHeader("X-Requested-With").equals("XMLHttpRequest")){
						print(String.format("{\"state\":\"login\"}"));
						return stayPage();
					}
					return redirectToAction("login","user","");
				}
				if (method.getAnnotation(Admin.class) != null && !u.isAdmin()) {
					return home();
				}
			}

			if (type.toLowerCase().equals("post") && param != null && param != "") {
				if (!getModel(param)) {
					return error();
				}
			} else {
				Model = getReqAttr("Model");
			}
			return type.toLowerCase().equals("post") ?
					(String) method.invoke(this) :
					(String) method.invoke(this, param)
				;
		} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
			if (method == null && type.toLowerCase().equals("get")) {
				return view(null);
			} else {
				e.printStackTrace();
			}
		}


		return error();
	}

	protected void print(String msg) {
		try {
			resp.getWriter().print(msg);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private boolean getModel(String name) {
		name = String.format("com.me.model.viewmodel.%s", name);
		try {
			Object o = Class.forName(name).newInstance();
			Method[] methods = o.getClass().getDeclaredMethods();
			Map<String, String[]> params = null;
			if (getReqAttr("items") == null) {
				params = req.getParameterMap();
			} else {
				params = new HashMap<>();
				for (FileItem item : (List<FileItem>)getReqAttr("items")){
					params.put(item.getFieldName(), new String[]{get(item.getFieldName()).toString()});
				}
			}
			for (String key : params.keySet()) {
				String filedName = key;
				String methodName = String.format("set%s", filedName.substring(0, 1).toUpperCase() + filedName.substring(1));
				for (Method m : methods) {
					if (m.getName().equals(methodName)) {
						m.invoke(o, (Object) params.get(key)[0]);
					}
				}
			}
			Model = o;
			return true;
		} catch (InstantiationException | IllegalAccessException | ClassNotFoundException | InvocationTargetException e) {
			return false;
		}
	}

	public void setRedirectParam() {
		if (isParamEnabled()) {
			for (Map.Entry<String, Object> p : ((Map<String, Object>)getSessionAttr("params")).entrySet()) {
				setReqAttr(p.getKey(), p.getValue());
			}
			resetParam();
		}
	}

	protected String getParam(String key) {
		return req.getParameter(key);
	}

	protected boolean getBooleanParam(String key) {
		return Boolean.parseBoolean(getParam(key));
	}

	protected Object get(String key) {
		if (req.getParameter(key) != null) {
			return getParam(key);
		}
		if (req.getAttribute(key) != null) {
			getReqAttr(key);
		}
		if (session.getAttribute(key) != null) {
			getSessionAttr(key);
		}
		List<FileItem> items = (List<FileItem>) req.getAttribute("items");
		if (items != null) {
			try {
				for (FileItem item : items) {
					if (item.getFieldName().equals(key)) {
						if (item.isFormField()) {
							return item.getString("utf8");
						} else {
							String savePath = req.getServletContext().getRealPath("/img/upload");
							return ServUtil.writeResource(item, savePath);
						}
					}
				}
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}

		return null;
	}

	protected int getIntegerParam(String key) {
		return Integer.parseInt(getParam(key));
	}

	protected String[] getParams(String key) {
		return req.getParameterValues(key);
	}

	protected Object getSessionAttr(String key) {
		return session.getAttribute(key);
	}

	protected void setSessionAttr(String key, Object val) {
		session.setAttribute(key, val);
	}

	protected void setReqAttr(String key, Object val) {
		req.setAttribute(key, val);
	}

	protected Object getReqAttr(String key) {
		return req.getAttribute(key);
	}

	protected void removeReqAttr(String key) {
		req.removeAttribute(key);
	}

	protected User getSessionUser() {
		return (User) getSessionAttr("user");
	}

	protected void clearSessionUser() {
		session.removeAttribute("user");
	}

	protected void setModel(Object model) {
		setRedirectParam("Model", model);
	}

	protected String redirectToAction(String methodName) {
		return redirectToAction(methodName, "");
	}

	protected String redirectToAction(String methodName, String param) {
		String cName = this.getClass().getSimpleName();
		return redirectToAction(methodName, cName.substring(0, cName.indexOf("Controller")).toLowerCase(), param);
	}

	protected String redirectToAction(String methodName, Object model) {
		setRedirectParam("Model", model);
		return redirectToAction(methodName,controller,"");
	}

	protected String redirectToAction(String methodName, Object model,String param) {
		setRedirectParam("Model", model);
		return redirectToAction(methodName,controller,param);
	}

	protected String redirectToAction(String methodName, String controllerName, String param) {
		return page(String.format(
			"./%s/%s/%s",
			controllerName.toLowerCase(),
			methodName,
			param
		));
	}

	protected List<FileItem> getBinAttrs() {
		return (List<FileItem>) getReqAttr("items");
	}

	protected String view(Object model) {
		return view(method,model);
	}

	protected String view(String page, Object model) {
		return view(page, controller,model);
	}

	protected String view(String page, String controller,Object model) {
		setReqAttr("Model",Model == null ? model : Model);
		return page(String.format("/%s/%s.jsp", controller, page));
	}

	protected String error() {
		return error(null);
	}

	protected String error(String msg) {
		setReqAttr("msg",msg);
		return page("/error.jsp");
	}

	protected String home() {
		return page("./");
	}

	protected String stayPage() {
		return page("...");
	}

	protected String page(String url) {
		return url;
	}
}
